package cn.marknum.gateway.filter;

import cn.hutool.core.net.URLDecoder;
import cn.hutool.core.util.StrUtil;
import cn.marknum.cas.client.session.AssertionStorage;
import cn.marknum.cas.client.util.AbstractCasFilter;
import cn.marknum.cas.client.validation.Assertion;
import cn.marknum.cas.client.validation.AssertionImpl;
import cn.marknum.feign.clients.TokenFeginClient;
import cn.marknum.redis.constants.RedisKey;
import cn.marknum.redis.pojo.TokenYh;
import cn.marknum.redis.service.TokenYhService;
import cn.marknum.web.ErrCode;
import cn.marknum.web.response.Response;
import cn.marknum.web.response.ResponseDSL;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

/**
 * 在cas登录后，为登录用户生成token
 *
 * @author zhangzhao    2021-08-31 15:55
 */
public class GenerateTokenAfterCasLoginFilter implements GlobalFilter, Ordered {

    private static final Logger LOGGER = LoggerFactory.getLogger(GenerateTokenAfterCasLoginFilter.class);
    private TokenFeginClient tokenFeginClient;
    private int order;
    private String logoutAddress;
    private AssertionStorage assertionStorage;
    private AuthUtil authUtil;
    private StringRedisTemplate stringRedisTemplate;
    private TokenYhService tokenYhService;
    private static ConcurrentHashMap<String, Object> userLocks = new ConcurrentHashMap<>();

    @Autowired
    public void setTokenYhService(TokenYhService tokenYhService) {
        this.tokenYhService = tokenYhService;
    }

    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public GenerateTokenAfterCasLoginFilter(TokenFeginClient tokenFeginClient, int order, String logoutAddress, AssertionStorage assertionStorage, AuthUtil authUtil) {
        this.order = order;
        this.tokenFeginClient = tokenFeginClient;
        this.logoutAddress = logoutAddress;
        this.assertionStorage = assertionStorage;
        this.authUtil = authUtil;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getPath().value();
        if (authUtil.filtration(path)) {
            LOGGER.debug("无需权限验证");
            Mono<Void> mono = chain.filter(exchange);
            return mono.doFinally(signalType -> {
                if (path.contains("sxjwt/login")) {
                    String userCredential = exchange.getRequest().getHeaders().getFirst("userCredential");
                    userCredential = URLDecoder.decode(userCredential, StandardCharsets.UTF_8);
                    try {
                        String jh = JSON.parseObject(userCredential).getJSONObject("credential").getJSONObject("load").getJSONObject("userInfo").getString("jh");
                        String token = exchange.getResponse().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
                        Assertion assertion = new AssertionImpl(jh);
                        assertionStorage.put(token, assertion);
                    } catch (Exception e) {
                        LOGGER.error("获取警号失败：{}", userCredential);
                    }
                }
            });
        }
        HttpHeaders header = exchange.getRequest().getHeaders();
        String token = header.getFirst(HttpHeaders.AUTHORIZATION);
        if (token == null) {
            LOGGER.debug("未获取到token，即将进行登录操作");
            Assertion assertion = exchange.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
            Principal principal = assertion.getPrincipal();
            String zh = principal.getName();
            if (StringUtils.isNotEmpty(zh)) {
                synchronized (userLocks.computeIfAbsent(zh, (s) -> new Object())) {
                    token = stringRedisTemplate.opsForValue().get(RedisKey.JCMK_YH_TOKEN + zh);
                    //验证token对应的用户是否存在，不存在则需重新登录
                    if (StrUtil.isNotBlank(token) && tokenYhService.findById(token) == null) {
                        stringRedisTemplate.expire(RedisKey.JCMK_YH_TOKEN + zh, 0, TimeUnit.SECONDS);
                        token = null;
                    }
                    if (StrUtil.isNotEmpty(token)) {
                        LOGGER.debug("Redis获取到用户TOKEN：{}", token);
                        return todoRequest(exchange, chain, assertion, token);
                    }
                    LOGGER.debug("准备登录");
                    ResponseEntity<Response> resp = tokenFeginClient.loginByThirdParty(zh);
                    if (ErrCode.NO_ERROR.getTipCode().equals(resp.getBody().getXym())) {
                        LOGGER.debug("登录成功");
                        String newToken = resp.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
                        return todoRequest(exchange, chain, assertion, newToken);
                    } else if (ErrCode.LOGIN_INFO_ERROR.getTipCode().equals(resp.getBody().getXym())) {
                        LOGGER.debug("登录账号不存在：{}", zh);
                        ServerHttpResponse response = exchange.getResponse();
                        response.setStatusCode(HttpStatus.OK);
                        response.getHeaders().add("Content-Type", "text/html;charset=UTF-8");
                        StringBuilder sb = new StringBuilder();

                        sb.append("<!DOCTYPE html>");
                        sb.append("<html lang=\"zh-CN\">");
                        sb.append("<head>");
                        sb.append("<meta charset=\"UTF-8\">");
                        sb.append("<title> </title>");
                        sb.append("<script type=\"text/javascript\">");
                        sb.append("alert('" + resp.getBody().getXyXx() + "');");
                        sb.append("location.href='" + logoutAddress + "';");
                        sb.append("</script>");
                        sb.append("</head>");
                        sb.append("<body></body></html>");

                        DataBuffer dataBuffer =
                                response.bufferFactory().wrap(sb.toString().getBytes(StandardCharsets.UTF_8));
                        return response.writeWith(Flux.just(dataBuffer));
                    } else {
                        LOGGER.debug("登录失败");
                        return getVoidMono(exchange.getResponse(), resp.getBody().getXym(), resp.getBody().getXyXx());
                    }
                }
            } else {
                LOGGER.error("cas登录后未获取到用户信息");
                return getVoidMono(exchange.getResponse(), ErrCode.SYS_CODE_ERROR.getTipCode(), ErrCode.SYS_CODE_ERROR.getTipMsg());
            }
        }
        return chain.filter(exchange);
    }

    private Mono<Void> todoRequest(ServerWebExchange exchange, GatewayFilterChain chain, Assertion assertion, String token) {
        assertionStorage.put(token, assertion);
        exchange.getResponse().getHeaders().set(HttpHeaders.AUTHORIZATION, token);
        ServerWebExchange newExchange = exchange.mutate()
                .request(exchange.getRequest().mutate().header(HttpHeaders.AUTHORIZATION, token).build())
                .build();
        return chain.filter(newExchange);
    }

    private Mono<Void> getVoidMono(ServerHttpResponse serverHttpResponse, String code, String message) {
        serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
        Response responseResult = ResponseDSL.failure(code, message);
        DataBuffer dataBuffer =
                serverHttpResponse.bufferFactory().wrap(JSONObject.toJSONString(responseResult).getBytes(StandardCharsets.UTF_8));
        return serverHttpResponse.writeWith(Flux.just(dataBuffer));
    }

    @Override
    public int getOrder() {
        return order;
    }
}
